library(tidyverse)
steam <- read_csv("raw_data/steam_checkpoint_2.csv")
Rows: 26564 Columns: 27── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (10): name, developer, publisher, controller_support, multiplayer, categories, genres, steamspy_tags, general_rating, owners
dbl (9): appid, required_age, achievements, positive_ratings, negative_ratings, average_playtime, median_playtime, price, total_reviews
lgl (7): free_to_play, virtual_reality_support, steam_workshop, singleplayer, windows_support, mac_support, linux_support
date (1): release_date
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) :
attempt to use zero-length variable name
steam
steam %>%
select(name, owners)
#For the purposes of making a model, what would we want the target to
be? If we were to create a column called “successful”, what variables
would it look for?
steam %>%
separate(col = steamspy_tags, sep = ";", into = c("tag_1", "tag_2", "tag_3")) %>%
pivot_longer(cols = c("tag_1", "tag_2", "tag_3"), values_to = "tags", names_to = "drop_me") %>%
drop_na(tags) %>%
group_by(tags) %>%
count(tags) %>%
arrange(desc(n))
Warning: Expected 3 pieces. Missing pieces filled with `NA` in 2551 rows [1283, 3566, 3669, 3711, 4035, 4146, 4457, 4733, 4822, 5019, 5140, 5142, 5264, 5361, 5378, 5497, 5553, 5556, 5570, 5756, ...].
# creating new logical columns for use in the model - we cant have every genre/tag, so a selection of a few will have to do
# Preferably ones without a lot of overlap
# Not including Indie just now, because anything developed by Dinkey game would be Indie by default - It's a tag that doesnt really describe anything about the game
steam <- steam %>%
mutate(is_open_world = ifelse(str_detect(steamspy_tags, "Open World"), TRUE, FALSE),
is_rogue_like = ifelse(str_detect(steamspy_tags, "Rogue-like"), TRUE, FALSE),
is_metroidvania = ifelse(str_detect(steamspy_tags, "Metroidvania"), TRUE, FALSE),
is_visual_novel = ifelse(str_detect(steamspy_tags, "Visual Novel"), TRUE, FALSE),
has_great_soundtrack = ifelse(str_detect(steamspy_tags, "Great Soundtrack"), TRUE, FALSE),
is_shooter =ifelse(str_detect(steamspy_tags, "Shooter") | str_detect(steamspy_tags, "FPS") | str_detect(steamspy_tags, "Third-Person Shooter"), TRUE, FALSE),
is_rpg = ifelse(str_detect(steamspy_tags, "RPG"), TRUE, FALSE),
has_female_protagonist = ifelse(str_detect(steamspy_tags, "Female Protagonist"), TRUE, FALSE),
is_sports = ifelse(str_detect(steamspy_tags, "Sports"), TRUE, FALSE),
is_simulation = ifelse(str_detect(steamspy_tags, "Simulation"), TRUE, FALSE),
.after = multiplayer)
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) :
attempt to use zero-length variable name
steam <- steam %>%
mutate(is_positive = ifelse(str_detect(general_rating, "Positive"), TRUE, FALSE),
.after = linux_support)
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) :
attempt to use zero-length variable name
For the purpose of my model over in Python land, i’m turning
Multiplayer into just a logical to see if it changes anything
steam %>%
distinct(multiplayer)
steam <- steam %>%
mutate(multiplayer = ifelse(str_detect(multiplayer, "No multiplayer"), FALSE, TRUE))
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) :
attempt to use zero-length variable name
#ok fine im adding indie to see what happens
steam <- steam %>%
mutate(is_indie = ifelse(str_detect(steamspy_tags, "Indie"), TRUE, FALSE))
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) :
attempt to use zero-length variable name
steam %>%
filter(multiplayer == FALSE)
# what if we bumped up the threshold? What if mostly positive was no longer acceptable?
steam_but_harsher <- steam %>%
mutate(is_positive = case_when(
general_rating == "Extremely Positive" | general_rating == "Positive" ~ TRUE,
TRUE ~ FALSE
))
write_csv(steam, "clean_data/steam_for_model.csv")
write_csv(steam_but_harsher, "clean_data/harsher_steam_for_model.csv")
For now, im gonna do some text stuff using backloggd reviews
backloggd_reviews <- read_csv("clean_data/backloggd_reviews.csv")
Rows: 1509 Columns: 8── Column specification ─────────────────────────────────────────────────────────────
Delimiter: ","
chr (7): title, summary, reviews, genre_tag, genre_tag_2, genre_tag_3, genre_tag_4
dbl (1): rating
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# separating reviews into their own columns so i can manage them
reviews_separated <- backloggd_reviews %>%
separate_longer_delim(cols = "reviews", delim = "',\ \'") %>%
separate_longer_delim(cols = "reviews", delim = "\',\ \"") %>%
separate_longer_delim(cols = "reviews", delim = "\",\ \'") %>%
separate_longer_delim(cols = "reviews", delim = "\",\ \"") %>%
unique() %>%
# removing all the opening and closing rubbish
mutate(reviews = str_remove_all(string = reviews, pattern = "\\[\""),
reviews = str_remove_all(string = reviews, pattern = "\\[\'"),
reviews = str_remove_all(string = reviews, pattern = "\"\\]"),
reviews = str_remove_all(string = reviews, pattern = "\'\\]"))
reviews_separated %>%
group_by(title) %>%
count(title)
lets split this into ratings, like i did with the other backloggd
thing
reviews_separated <- reviews_separated %>%
mutate(rating_range = case_when(
rating < 5 & rating >= 4 ~ "4+",
rating < 4 & rating >= 3 ~ "3 to 4",
rating < 3 & rating >= 2 ~ "2 to 3",
rating < 2 & rating >= 1 ~ "1 to 2",
rating < 1 ~ ">1",
TRUE ~ "No rating"
))
Before doing text analysis, lets convert everything to lower case -
this might take away some context via capitalisation, but we only have 6
reviews per game so gotta work with what we have
reviews_lower_separated <- reviews_separated %>%
mutate(reviews = tolower(reviews)) #%>%
#filter(reviews %in% c("í", "ñ", "é", "á", "ó", "á") == FALSE)
library(textdata)
library(tidytext)
de_stopwords <- tibble(word = stopwords("de"))
Error in stopwords("de") : could not find function "stopwords"

reviews_3_to_lowest_stop <- reviews_lower_separated %>%
filter(rating_range == "2 to 3" | rating_range == "1 to 2" | rating_range == ">1") %>%
select(-summary) %>%
unnest_tokens(input = reviews, output = word) %>%
anti_join(filter(stop_words, lexicon == "SMART")) %>%
anti_join(game_stop_words) %>%
anti_join(pt_stopwords) %>%
anti_join(de_stopwords) %>%
count(word, sort = TRUE)
Joining with `by = join_by(word)`Joining with `by = join_by(word)`Joining with `by = join_by(word)`Joining with `by = join_by(word)`

bigrams might be more appropriate for bad reviews
bigrams_3_to_lowest_stop <- reviews_lower_separated %>%
filter(rating_range == "2 to 3" | rating_range == "1 to 2" | rating_range == ">1") %>%
unnest_tokens(bigram, reviews, token = "ngrams", n = 2) %>%
count(bigram, sort = TRUE) %>% # count bigrams
separate(bigram, into = c("word1", "word2"), sep = " ") %>% # split bigrams into two seperate columns
anti_join(stop_words, join_by("word1" == "word")) %>% # check if word1 of bigram is a stop word
anti_join(stop_words, join_by("word2" == "word")) %>%
anti_join(pt_stopwords, join_by("word1" == "word")) %>%
anti_join(pt_stopwords, join_by("word2" == "word")) %>%
anti_join(game_stop_words, join_by("word1" == "word")) %>%
anti_join(game_stop_words, join_by("word2" == "word")) %>%
anti_join(de_stopwords, join_by("word1" == "word")) %>%
anti_join(de_stopwords, join_by("word2" == "word")) %>%
drop_na() %>%
unite(col = "bigram", word1:word2, sep = " ", remove = TRUE) %>%
anti_join(game_stop_bigrams)
Joining with `by = join_by(bigram)`

well fine lets remove game names as well
ok that still kinda sucks
ggwordcloud(words = bigrams_3_to_5_no_stop$bigram, freq = bigrams_3_to_5_no_stop$n, random.color = TRUE, colors = c("#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93"))

ok what if we try some of that tf-idf stuff
``
LS0tDQp0aXRsZTogIkRheSBUaGUgRm91ciINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0KYGBge3J9DQpzdGVhbSA8LSByZWFkX2NzdigicmF3X2RhdGEvc3RlYW1fY2hlY2twb2ludF8yLmNzdiIpDQpgYGANCg0KYGBge3J9DQpzdGVhbQ0KYGBgDQoNCmBgYHtyfQ0Kc3RlYW0gJT4lIA0KICBzZWxlY3QobmFtZSwgb3duZXJzKQ0KYGBgDQoNCiNGb3IgdGhlIHB1cnBvc2VzIG9mIG1ha2luZyBhIG1vZGVsLCB3aGF0IHdvdWxkIHdlIHdhbnQgdGhlIHRhcmdldCB0byBiZT8gSWYgd2Ugd2VyZSB0byBjcmVhdGUgYSBjb2x1bW4gY2FsbGVkICJzdWNjZXNzZnVsIiwgd2hhdCB2YXJpYWJsZXMgd291bGQgaXQgbG9vayBmb3I/DQoNCmBgYHtyfQ0KDQpzdGVhbSAlPiUNCiAgc2VwYXJhdGUoY29sID0gc3RlYW1zcHlfdGFncywgc2VwID0gIjsiLCBpbnRvID0gYygidGFnXzEiLCAidGFnXzIiLCAidGFnXzMiKSkgJT4lIA0KICBwaXZvdF9sb25nZXIoY29scyA9IGMoInRhZ18xIiwgInRhZ18yIiwgInRhZ18zIiksIHZhbHVlc190byA9ICJ0YWdzIiwgbmFtZXNfdG8gPSAiZHJvcF9tZSIpICU+JSANCiAgZHJvcF9uYSh0YWdzKSAlPiUgDQogIGdyb3VwX2J5KHRhZ3MpICU+JSANCiAgIGNvdW50KHRhZ3MpICU+JSANCiAgIGFycmFuZ2UoZGVzYyhuKSkgDQoNCg0KYGBgDQoNCmBgYHtyfQ0KIyBjcmVhdGluZyBuZXcgbG9naWNhbCBjb2x1bW5zIGZvciB1c2UgaW4gdGhlIG1vZGVsIC0gd2UgY2FudCBoYXZlIGV2ZXJ5IGdlbnJlL3RhZywgc28gYSBzZWxlY3Rpb24gb2YgYSBmZXcgd2lsbCBoYXZlIHRvIGRvDQojIFByZWZlcmFibHkgb25lcyB3aXRob3V0IGEgbG90IG9mIG92ZXJsYXANCiMgTm90IGluY2x1ZGluZyBJbmRpZSBqdXN0IG5vdywgYmVjYXVzZSBhbnl0aGluZyBkZXZlbG9wZWQgYnkgRGlua2V5IGdhbWUgd291bGQgYmUgSW5kaWUgYnkgZGVmYXVsdCAtIEl0J3MgYSB0YWcgdGhhdCBkb2VzbnQgcmVhbGx5IGRlc2NyaWJlIGFueXRoaW5nIGFib3V0IHRoZSBnYW1lDQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogIG11dGF0ZShpc19vcGVuX3dvcmxkID0gaWZlbHNlKHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIk9wZW4gV29ybGQiKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgaXNfcm9ndWVfbGlrZSA9IGlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJSb2d1ZS1saWtlIiksIFRSVUUsIEZBTFNFKSwNCiAgICAgICAgIGlzX21ldHJvaWR2YW5pYSA9IGlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJNZXRyb2lkdmFuaWEiKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgaXNfdmlzdWFsX25vdmVsID0gaWZlbHNlKHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIlZpc3VhbCBOb3ZlbCIpLCBUUlVFLCBGQUxTRSksDQogICAgICAgICBoYXNfZ3JlYXRfc291bmR0cmFjayA9IGlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJHcmVhdCBTb3VuZHRyYWNrIiksIFRSVUUsIEZBTFNFKSwNCiAgICAgICAgIGlzX3Nob290ZXIgPWlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJTaG9vdGVyIikgfCBzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJGUFMiKSB8IHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIlRoaXJkLVBlcnNvbiBTaG9vdGVyIiksIFRSVUUsIEZBTFNFKSwNCiAgICAgICAgIGlzX3JwZyA9IGlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJSUEciKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgaGFzX2ZlbWFsZV9wcm90YWdvbmlzdCA9IGlmZWxzZShzdHJfZGV0ZWN0KHN0ZWFtc3B5X3RhZ3MsICJGZW1hbGUgUHJvdGFnb25pc3QiKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgaXNfc3BvcnRzID0gaWZlbHNlKHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIlNwb3J0cyIpLCBUUlVFLCBGQUxTRSksDQogICAgICAgICBpc19zaW11bGF0aW9uID0gaWZlbHNlKHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIlNpbXVsYXRpb24iKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuYWZ0ZXIgPSBtdWx0aXBsYXllcikgDQoNCmBgYA0KYGBge3J9DQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogIG11dGF0ZShpc19wb3NpdGl2ZSA9IGlmZWxzZShzdHJfZGV0ZWN0KGdlbmVyYWxfcmF0aW5nLCAiUG9zaXRpdmUiKSwgVFJVRSwgRkFMU0UpLA0KICAgICAgICAgLmFmdGVyID0gbGludXhfc3VwcG9ydCkNCmBgYA0KRm9yIHRoZSBwdXJwb3NlIG9mIG15IG1vZGVsIG92ZXIgaW4gUHl0aG9uIGxhbmQsIGknbSB0dXJuaW5nIE11bHRpcGxheWVyIGludG8ganVzdCBhIGxvZ2ljYWwgdG8gc2VlIGlmIGl0IGNoYW5nZXMgYW55dGhpbmcNCg0KYGBge3J9DQpzdGVhbSAlPiUgDQogIGRpc3RpbmN0KG11bHRpcGxheWVyKQ0KYGBgDQoNCg0KYGBge3J9DQpzdGVhbSA8LSBzdGVhbSAlPiUgDQogIG11dGF0ZShtdWx0aXBsYXllciA9IGlmZWxzZShzdHJfZGV0ZWN0KG11bHRpcGxheWVyLCAiTm8gbXVsdGlwbGF5ZXIiKSwgRkFMU0UsIFRSVUUpKSANCmBgYA0KYGBge3J9DQojb2sgZmluZSBpbSBhZGRpbmcgaW5kaWUgdG8gc2VlIHdoYXQgaGFwcGVucyANCnN0ZWFtIDwtIHN0ZWFtICU+JSANCiAgbXV0YXRlKGlzX2luZGllID0gaWZlbHNlKHN0cl9kZXRlY3Qoc3RlYW1zcHlfdGFncywgIkluZGllIiksIFRSVUUsIEZBTFNFKSkgDQpgYGANCmBgYHtyfQ0Kc3RlYW0gJT4lIA0KICBmaWx0ZXIobXVsdGlwbGF5ZXIgPT0gRkFMU0UpDQpgYGANCmBgYHtyfQ0KIyB3aGF0IGlmIHdlIGJ1bXBlZCB1cCB0aGUgdGhyZXNob2xkPyBXaGF0IGlmIG1vc3RseSBwb3NpdGl2ZSB3YXMgbm8gbG9uZ2VyIGFjY2VwdGFibGU/DQoNCnN0ZWFtX2J1dF9oYXJzaGVyIDwtIHN0ZWFtICU+JSANCiAgbXV0YXRlKGlzX3Bvc2l0aXZlID0gY2FzZV93aGVuKA0KICAgIGdlbmVyYWxfcmF0aW5nID09ICJFeHRyZW1lbHkgUG9zaXRpdmUiIHwgZ2VuZXJhbF9yYXRpbmcgPT0gIlBvc2l0aXZlIiB+IFRSVUUsDQogICAgVFJVRSB+IEZBTFNFDQogICkpIA0KYGBgDQoNCmBgYHtyfQ0Kc3RlYW1fYnV0X2hhcnNoZXIgJT4lIA0KICBmaWx0ZXIoaXNfcG9zaXRpdmUgPT0gVFJVRSkNCmBgYA0KYGBge3J9DQpzdGVhbSAlPiUgDQogIGZpbHRlcihpc19wb3NpdGl2ZSA9PSBUUlVFKQ0KYGBgDQoNCg0KYGBge3J9DQp3cml0ZV9jc3Yoc3RlYW0sICJjbGVhbl9kYXRhL3N0ZWFtX2Zvcl9tb2RlbC5jc3YiKQ0Kd3JpdGVfY3N2KHN0ZWFtX2J1dF9oYXJzaGVyLCAiY2xlYW5fZGF0YS9oYXJzaGVyX3N0ZWFtX2Zvcl9tb2RlbC5jc3YiKQ0KYGBgDQoNCl9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXw0KDQojIEZvciBub3csIGltIGdvbm5hIGRvIHNvbWUgdGV4dCBzdHVmZiB1c2luZyBiYWNrbG9nZ2QgcmV2aWV3cw0KDQpgYGB7cn0NCmJhY2tsb2dnZF9yZXZpZXdzIDwtIHJlYWRfY3N2KCJjbGVhbl9kYXRhL2JhY2tsb2dnZF9yZXZpZXdzLmNzdiIpDQpgYGANCg0KYGBge3J9DQojIHNlcGFyYXRpbmcgcmV2aWV3cyBpbnRvIHRoZWlyIG93biBjb2x1bW5zIHNvIGkgY2FuIG1hbmFnZSB0aGVtDQpyZXZpZXdzX3NlcGFyYXRlZCA8LSBiYWNrbG9nZ2RfcmV2aWV3cyAlPiUgDQogIHNlcGFyYXRlX2xvbmdlcl9kZWxpbShjb2xzID0gInJldmlld3MiLCBkZWxpbSA9ICInLFwgXCciKSAlPiUgDQogIHNlcGFyYXRlX2xvbmdlcl9kZWxpbShjb2xzID0gInJldmlld3MiLCBkZWxpbSA9ICJcJyxcIFwiIikgJT4lIA0KICBzZXBhcmF0ZV9sb25nZXJfZGVsaW0oY29scyA9ICJyZXZpZXdzIiwgZGVsaW0gPSAiXCIsXCBcJyIpICU+JSAgDQogIHNlcGFyYXRlX2xvbmdlcl9kZWxpbShjb2xzID0gInJldmlld3MiLCBkZWxpbSA9ICJcIixcIFwiIikgJT4lIA0KICB1bmlxdWUoKSAlPiUgDQogICMgcmVtb3ZpbmcgYWxsIHRoZSBvcGVuaW5nIGFuZCBjbG9zaW5nIHJ1YmJpc2gNCiAgbXV0YXRlKHJldmlld3MgPSBzdHJfcmVtb3ZlX2FsbChzdHJpbmcgPSByZXZpZXdzLCBwYXR0ZXJuID0gIlxcW1wiIiksDQogICAgICAgICByZXZpZXdzID0gc3RyX3JlbW92ZV9hbGwoc3RyaW5nID0gcmV2aWV3cywgcGF0dGVybiA9ICJcXFtcJyIpLA0KICAgICAgICAgcmV2aWV3cyA9IHN0cl9yZW1vdmVfYWxsKHN0cmluZyA9IHJldmlld3MsIHBhdHRlcm4gPSAiXCJcXF0iKSwNCiAgICAgICAgIHJldmlld3MgPSBzdHJfcmVtb3ZlX2FsbChzdHJpbmcgPSByZXZpZXdzLCBwYXR0ZXJuID0gIlwnXFxdIikpIA0KDQpgYGANCg0KYGBge3J9DQpyZXZpZXdzX3NlcGFyYXRlZCAlPiUgDQogIGdyb3VwX2J5KHRpdGxlKSAlPiUgDQogIGNvdW50KHRpdGxlKQ0KYGBgDQoNCiMgbGV0cyBzcGxpdCB0aGlzIGludG8gcmF0aW5ncywgbGlrZSBpIGRpZCB3aXRoIHRoZSBvdGhlciBiYWNrbG9nZ2QgdGhpbmcNCmBgYHtyfQ0KcmV2aWV3c19zZXBhcmF0ZWQgPC0gcmV2aWV3c19zZXBhcmF0ZWQgJT4lIA0KICBtdXRhdGUocmF0aW5nX3JhbmdlID0gY2FzZV93aGVuKA0KICAgIHJhdGluZyA8IDUgJiByYXRpbmcgPj0gNCB+ICI0KyIsDQogICAgcmF0aW5nIDwgNCAmIHJhdGluZyA+PSAzIH4gIjMgdG8gNCIsDQogICAgcmF0aW5nIDwgMyAmIHJhdGluZyA+PSAyIH4gIjIgdG8gMyIsDQogICAgcmF0aW5nIDwgMiAmIHJhdGluZyA+PSAxIH4gIjEgdG8gMiIsDQogICAgcmF0aW5nIDwgMSB+ICI+MSIsDQogICAgVFJVRSB+ICJObyByYXRpbmciDQogICkpDQpgYGANCg0KQmVmb3JlIGRvaW5nIHRleHQgYW5hbHlzaXMsIGxldHMgY29udmVydCBldmVyeXRoaW5nIHRvIGxvd2VyIGNhc2UgLSB0aGlzIG1pZ2h0IHRha2UgYXdheSBzb21lIGNvbnRleHQgdmlhIGNhcGl0YWxpc2F0aW9uLCBidXQgd2Ugb25seSBoYXZlDQo2IHJldmlld3MgcGVyIGdhbWUgc28gZ290dGEgd29yayB3aXRoIHdoYXQgd2UgaGF2ZQ0KDQpgYGB7cn0NCnJldmlld3NfbG93ZXJfc2VwYXJhdGVkIDwtIHJldmlld3Nfc2VwYXJhdGVkICU+JSANCiAgbXV0YXRlKHJldmlld3MgPSB0b2xvd2VyKHJldmlld3MpKSAjJT4lIA0KICAjZmlsdGVyKHJldmlld3MgJWluJSBjKCLDrSIsICLDsSIsICLDqSIsICLDoSIsICLDsyIsICLDoSIpID09IEZBTFNFKQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeSh0ZXh0ZGF0YSkNCmxpYnJhcnkodGlkeXRleHQpDQpgYGANCg0KYGBge3J9DQpkZV9zdG9wd29yZHMgPC0gdGliYmxlKHdvcmQgPSBzdG9wd29yZHMoImRlIikpDQoNCnJldmlld3NfM190b181X3N0b3AgPC0gcmV2aWV3c19sb3dlcl9zZXBhcmF0ZWQgJT4lIA0KICBmaWx0ZXIocmF0aW5nX3JhbmdlID09ICI0KyIgfCByYXRpbmdfcmFuZ2UgPT0gIjMgdG8gNCIpICU+JSANCiAgc2VsZWN0KC1zdW1tYXJ5KSAlPiUgDQogIHVubmVzdF90b2tlbnMoaW5wdXQgPSByZXZpZXdzLCBvdXRwdXQgPSB3b3JkKSAlPiUgDQogIGFudGlfam9pbihmaWx0ZXIoc3RvcF93b3JkcywgbGV4aWNvbiA9PSAiU01BUlQiKSkgJT4lIA0KICBhbnRpX2pvaW4oZ2FtZV9zdG9wX3dvcmRzKSAlPiUgDQogIGFudGlfam9pbihwdF9zdG9wd29yZHMpICU+JSANCiAgYW50aV9qb2luKGRlX3N0b3B3b3JkcykgJT4lIA0KICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkNCiAgDQpgYGANCg0KYGBge3J9DQpnZ3dvcmRjbG91ZCh3b3JkcyA9IHJldmlld3NfM190b181X3N0b3Akd29yZCwgZnJlcSA9IHJldmlld3NfM190b181X3N0b3AkbiwgcmFuZG9tLmNvbG9yID0gVFJVRSwgDQogICAgICAgICAgICBjb2xvcnMgPSAgYygiI2ZmNTk1ZSIsICIjZmZjYTNhIiwgIiM4YWM5MjYiLCAiIzE5ODJjNCIsICIjNmE0YzkzIiksIG1pbi5mcmVxID0gMTApDQpgYGANCg0KYGBge3J9DQpyZXZpZXdzXzNfdG9fNV9zdG9wDQpgYGANCmBgYHtyfQ0KcmV2aWV3c18zX3RvX2xvd2VzdF9zdG9wIDwtIHJldmlld3NfbG93ZXJfc2VwYXJhdGVkICU+JSANCiAgZmlsdGVyKHJhdGluZ19yYW5nZSA9PSAiMiB0byAzIiB8IHJhdGluZ19yYW5nZSA9PSAiMSB0byAyIiB8IHJhdGluZ19yYW5nZSA9PSAiPjEiKSAlPiUgDQogIHNlbGVjdCgtc3VtbWFyeSkgJT4lIA0KICB1bm5lc3RfdG9rZW5zKGlucHV0ID0gcmV2aWV3cywgb3V0cHV0ID0gd29yZCkgJT4lIA0KICBhbnRpX2pvaW4oZmlsdGVyKHN0b3Bfd29yZHMsIGxleGljb24gPT0gIlNNQVJUIikpICU+JSANCiAgYW50aV9qb2luKGdhbWVfc3RvcF93b3JkcykgJT4lIA0KICBhbnRpX2pvaW4ocHRfc3RvcHdvcmRzKSAlPiUgDQogIGFudGlfam9pbihkZV9zdG9wd29yZHMpICU+JSANCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpDQoNCmBgYA0KDQpgYGB7cn0NCmdnd29yZGNsb3VkKHdvcmRzID0gcmV2aWV3c18zX3RvX2xvd2VzdF9zdG9wJHdvcmQsIGZyZXEgPSByZXZpZXdzXzNfdG9fbG93ZXN0X3N0b3AkbiwgcmFuZG9tLmNvbG9yID0gVFJVRSwgDQogICAgICAgICAgICBjb2xvcnMgPSAgYygiI2ZmNTk1ZSIsICIjZmZjYTNhIiwgIiM4YWM5MjYiLCAiIzE5ODJjNCIsICIjNmE0YzkzIiksIG1pbi5mcmVxID0gNSkNCmBgYA0KYmlncmFtcyBtaWdodCBiZSBtb3JlIGFwcHJvcHJpYXRlIGZvciBiYWQgcmV2aWV3cw0KDQpgYGB7cn0NCmJpZ3JhbXNfM190b19sb3dlc3Rfc3RvcCA8LSByZXZpZXdzX2xvd2VyX3NlcGFyYXRlZCAlPiUgDQogIGZpbHRlcihyYXRpbmdfcmFuZ2UgPT0gIjIgdG8gMyIgfCByYXRpbmdfcmFuZ2UgPT0gIjEgdG8gMiIgfCByYXRpbmdfcmFuZ2UgPT0gIj4xIikgJT4lIA0KICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgcmV2aWV3cywgdG9rZW4gPSAibmdyYW1zIiwgbiA9IDIpICU+JSANCiAgY291bnQoYmlncmFtLCBzb3J0ID0gVFJVRSkgJT4lICAjIGNvdW50IGJpZ3JhbXMNCiAgc2VwYXJhdGUoYmlncmFtLCBpbnRvID0gYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiKSAlPiUgIyBzcGxpdCBiaWdyYW1zIGludG8gdHdvIHNlcGVyYXRlIGNvbHVtbnMNCiAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGpvaW5fYnkoIndvcmQxIiA9PSAid29yZCIpKSAlPiUgICMgY2hlY2sgaWYgd29yZDEgb2YgYmlncmFtIGlzIGEgc3RvcCB3b3JkDQogIGFudGlfam9pbihzdG9wX3dvcmRzLCBqb2luX2J5KCJ3b3JkMiIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4ocHRfc3RvcHdvcmRzLCBqb2luX2J5KCJ3b3JkMSIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4ocHRfc3RvcHdvcmRzLCBqb2luX2J5KCJ3b3JkMiIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4oZ2FtZV9zdG9wX3dvcmRzLCBqb2luX2J5KCJ3b3JkMSIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4oZ2FtZV9zdG9wX3dvcmRzLCBqb2luX2J5KCJ3b3JkMiIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4oZGVfc3RvcHdvcmRzLCBqb2luX2J5KCJ3b3JkMSIgPT0gIndvcmQiKSkgJT4lIA0KICBhbnRpX2pvaW4oZGVfc3RvcHdvcmRzLCBqb2luX2J5KCJ3b3JkMiIgPT0gIndvcmQiKSkgJT4lIA0KICBkcm9wX25hKCkgJT4lIA0KICB1bml0ZShjb2wgPSAiYmlncmFtIiwgd29yZDE6d29yZDIsIHNlcCA9ICIgIiwgcmVtb3ZlID0gVFJVRSkgJT4lIA0KICBhbnRpX2pvaW4oZ2FtZV9zdG9wX2JpZ3JhbXMpDQpgYGANCg0KYGBge3J9DQpnZ3dvcmRjbG91ZCh3b3JkcyA9IGJpZ3JhbXNfM190b19sb3dlc3Rfc3RvcCRiaWdyYW0sIGZyZXEgPSBiaWdyYW1zXzNfdG9fbG93ZXN0X3N0b3AkbiwgcmFuZG9tLmNvbG9yID0gVFJVRSwgDQogICAgICAgICAgICBjb2xvcnMgPSAgYygiI2ZmNTk1ZSIsICIjZmZjYTNhIiwgIiM4YWM5MjYiLCAiIzE5ODJjNCIsICIjNmE0YzkzIiksIG1pbi5mcmVxID0gMSkNCmBgYA0Kd2VsbCBmaW5lIGxldHMgcmVtb3ZlIGdhbWUgbmFtZXMgYXMgd2VsbA0KDQpvayB0aGF0IHN0aWxsIGtpbmRhIHN1Y2tzDQoNCmBgYHtyfQ0KZ2FtZV9zdG9wX2JpZ3JhbXMgPC0gcmV2aWV3c19iYWNrbG9nZ2QgJT4lIA0KICBzZWxlY3QodGl0bGUpICU+JSANCiAgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHRpdGxlLCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lIA0KICBkcm9wX25hKCkNCmBgYA0KDQoNCmBgYHtyfQ0KcmV2aWV3c18zX3RvX2xvd2VzdF9zdG9wDQpgYGANCmBgYHtyfQ0KZ2FtZV9zdG9wX3dvcmRzIDwtIHRpYmJsZSgNCiAgd29yZCA9IGMoImdhbWUiLCAiZ2FtZXBsYXkiLCAidGltZSIsICJwbGF5ZWQiLCAicGxheSIsICJkZSIsICJwbGF5aW5nIiwgImpvZ28iLCAiw6kiLCAiZ2FtZXMiKSAjIGxldHMgdHJ5IGRvaW5nIG4tZ3JhbXMgYmVmb3JlIHBvcHVsYXRpbmcgdGhpcw0KKQ0KYGBgDQoNCg0KYGBge3J9DQpsaWJyYXJ5KHN0b3B3b3JkcykNCg0KZXNfc3RvcHdvcmRzIDwtIHRpYmJsZSh3b3JkID0gc3RvcHdvcmRzKCJlcyIpKQ0KcHRfc3RvcHdvcmRzIDwtIHRpYmJsZSh3b3JkID0gc3RvcHdvcmRzKCJwdCIpKQ0KYGBgDQoNCg0KYGBge3J9DQpiaWdyYW1zXzNfdG9fNV9ub19zdG9wIDwtIHJldmlld3NfbG93ZXJfc2VwYXJhdGVkICU+JSANCiAgZmlsdGVyKHJhdGluZ19yYW5nZSA9PSAiNCsiIHwgcmF0aW5nX3JhbmdlID09ICIzIHRvIDQiKSAlPiUgDQogIHVubmVzdF90b2tlbnMoYmlncmFtLCByZXZpZXdzLCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lIA0KICBjb3VudChiaWdyYW0sIHNvcnQgPSBUUlVFKSAlPiUgICMgY291bnQgYmlncmFtcw0KICBzZXBhcmF0ZShiaWdyYW0sIGludG8gPSBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpICU+JSAjIHNwbGl0IGJpZ3JhbXMgaW50byB0d28gc2VwZXJhdGUgY29sdW1ucw0KICBhbnRpX2pvaW4oc3RvcF93b3Jkcywgam9pbl9ieSgid29yZDEiID09ICJ3b3JkIikpICU+JSAgIyBjaGVjayBpZiB3b3JkMSBvZiBiaWdyYW0gaXMgYSBzdG9wIHdvcmQNCiAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGpvaW5fYnkoIndvcmQyIiA9PSAid29yZCIpKSAlPiUgDQogIGFudGlfam9pbihwdF9zdG9wd29yZHMsIGpvaW5fYnkoIndvcmQxIiA9PSAid29yZCIpKSAlPiUgDQogIGFudGlfam9pbihwdF9zdG9wd29yZHMsIGpvaW5fYnkoIndvcmQyIiA9PSAid29yZCIpKSAlPiUgDQogIGFudGlfam9pbihnYW1lX3N0b3Bfd29yZHMsIGpvaW5fYnkoIndvcmQxIiA9PSAid29yZCIpKSAlPiUgDQogIGFudGlfam9pbihnYW1lX3N0b3Bfd29yZHMsIGpvaW5fYnkoIndvcmQyIiA9PSAid29yZCIpKSAlPiUgDQogIGRyb3BfbmEoKSAlPiUgDQogIHVuaXRlKGNvbCA9ICJiaWdyYW0iLCB3b3JkMTp3b3JkMiwgc2VwID0gIiAiLCByZW1vdmUgPSBUUlVFKQ0KDQpiaWdyYW1zXzNfdG9fNV93X3N0b3AgPC0gcmV2aWV3c19sb3dlcl9zZXBhcmF0ZWQgJT4lIA0KICBmaWx0ZXIocmF0aW5nX3JhbmdlID09ICI0KyIgfCByYXRpbmdfcmFuZ2UgPT0gIjMgdG8gNCIpICU+JSANCiAgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHJldmlld3MsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKSAlPiUgICMgY3JlYXRlIGJpZ3JhbXMNCiAgY291bnQoYmlncmFtLCBzb3J0ID0gVFJVRSkgDQoNCmBgYA0KDQoNCmBgYHtyfQ0KICBnZ3dvcmRjbG91ZCh3b3JkcyA9IGJpZ3JhbXNfM190b181X25vX3N0b3AkYmlncmFtLCBmcmVxID0gYmlncmFtc18zX3RvXzVfbm9fc3RvcCRuLCByYW5kb20uY29sb3IgPSBUUlVFLCBjb2xvcnMgPSAgYygiI2ZmNTk1ZSIsICIjZmZjYTNhIiwgIiM4YWM5MjYiLCAiIzE5ODJjNCIsICIjNmE0YzkzIikpDQpgYGANCg0Kb2sgd2hhdCBpZiB3ZSB0cnkgc29tZSBvZiB0aGF0IHRmLWlkZiBzdHVmZg0KDQpgYGB7cn0NCnJldmlld3Nfc2VwYXJhdGVkICU+JSANCiAgdW5uZXN0X3Rva2VucyhpbnB1dCA9IHJldmlld3MsIG91dHB1dCA9IHdvcmQpDQpgYGANCmBgDQo=